Beginning Programming Series

Part X: Follow the Bouncing Ball

© 2004, Brad Moore

author contact:

http://www.freewebs.com/lb-connection

NL127 Home

Beginning Programming X

Multiple Listbox Arrays

Video Capture in LB

Images on Statictext

Bulk File Renamer

Dynamic Web Pages

Animated Titles

Financial Functions

::::::::::::::::::::::::::::::::::::::::::::::::::::

Submission Guildlines

Newsletter Help

Index

Introduction | Let's Get Drawing | Follow the bouncing ball | Getting Control! | Appendix A | Appendix B




The next thing I wanted to do with the program was develop it to the point where the ball will bounce realistically off of the walls. This requires some up front thought and planning to implement. Let's consider what is happening when the ball is bouncing:

In the graphic you can see the projected path of a ball with a fairly large angle of deflection. Remember, each time the ball is drawn on the screen, we need an x and y coordinate. As the ball moves, the x and y coordinates are changing. Consider the movement here. In the first path (moving down and left) the x coordinate is decreasing with each cycle of the animation. At the same time the y coordinate is increasing (as the ball moves away from the zero coordinate). After the ball hits the wall and rebounds, moving up the second path, the x coordinate continues to decrease (because the ball is moving left toward zero), but now the y coordinate begins decreasing as the ball moves upward. Finally, the ball hits the side wall. At this point the x coordinate begins to increase, as the y coordinate continues to decrease. Soon the ball will strike the upper wall and bounce off in another direction again. I don't show that, but we can deduce that when it happens, the y coordinate will begin to increase, as the ball will be going down. The x coordinate will continue to increase like is has been, as the ball is still traveling toward the left.

This can be a complex set of interactions to consider. Even as tough as they are to think about (I am always getting myself turned around when I do), they can become even more confusing to code. To be successful, we will need to break the whole process into smaller parts, mapping them out with pseudo code as we go.

Since this will be designed to run until the user clicks the close window button, we will depart from using a FOR-NEXT loop which by definition is finite. In place of it we will use Liberty Basic's timer, which will have the additional benefit of providing more consistent timing across many computer systems with various system speeds. We will look at this in depth in a bit further in the article. For now, let's just say we will update the ball draw cycle every 50 milliseconds.

So each time we update the draw cycle we will do the following:

  1. Save the current x and y location.
  2. Calculate a new x and y location - factoring the required ball movement in both the x and y location.
  3. Draw a white ball at the old ball location (saved in step 1) erasing the red ball
  4. Draw a red ball at the new ball location (calculated in step 2)
  5. Check for a boundary collision (did we hit an edge?)
  6. If not, wait for the next event

That will be the heart of the ball drawing code that causes the animation to happen. We need to deal with x boundary and y boundary collisions. I have decided to also factor in a chance of variation in speed and degree of change in the y direction each time the ball hits either the left or right wall. This will actually come to add more complexity than I bargained for, but the effect is great.

Handling a y boundary collision is pretty simple. We simply need to reverse the direction that the ball is moving in the y direction. We will be using a y modifier (it will be called ychg) to hold the value that y will change each cycle. Whether the ball is moving up or down depends on whether this y modifier is positive or negative. An easy way to flip it is to multiply itself by a minus one. As any algebra student knows, any value multiplied by a negative one is always the same value with the opposite sign.

That was the easy part. When the ball hits either the left or right wall, I want to perform the following tasks:

  1. Change the x direction
  2. Alter the speed of the ball (that is the value of the x modifier - it will be called xchg) - remember the apparent speed of the ball will be how many pixels it travels per cycle.
  3. Alter the deflection angle of the ball (in the y direction). This will allow us to slightly change the bouncing action of the ball (making it more or less bouncy in a random manner) when it hits the wall. This is done by changing the y modifier, which we said will be called ychg.

As the case is, there will surely be some adjustments as we go along. Let's start the coding of our animated ball program. As you will remember from a bit earlier, we mentioned that we would be moving away for using a FOR-NEXT loop to control the animation effect, and instead use Liberty Basic TIMER function. This will give us several benefits:

timing of the animation will be consistent across many different classes of PCs where this might run.
will allow us to run the animation indefinitely.
will allow us to easily trap the close window event when the user is done viewing the animation.

The timer function is really a timed triggered event. It is an event just like when the user presses a command button and causes some code to be executed that is related to the button. Remember, we call these units of code and "event handler" and that when the user clicks the button we say they have triggered an event. The timer can trigger an event at specified intervals without the user clicking or interacting with the program in anyway. As events go, we will spend the majority of the computer time waiting, which is good, because it allows us to handle other events also. Here is what the Liberty Basic Helpfile says about TIMER:

TIMER milliseconds, [branchLabel]

Timer milliseconds subName

Description:

This commands manages a Windows timer. This is useful for controlling the rate of software execution (games or animation perhaps), or for creating a program or program feature which activates periodically (a clock perhaps, or an email client which checks for new messages). The TIMER is deactivated by setting a time value of 0, and no branch label. There is only one timer. The elapsed time value and/or branch label to execute can be changed at any time by issuing a new TIMER command. There are 1000 milliseconds in one second. A value of 1000 causes the timer to fire every one second. A value of 500 causes the timer to fire every half second, and so on.

So, it is a bit detailed, but basically what you get is the ability to control the firing of the event down to the millisecond level. You also get to specify the code that will act as the event handler when the event is fired. In Liberty Basic version 4.x, this can be either a labeled unit of code, or a sub program. Earlier versions of Liberty Basic only support labeled units of code.

The helpfile does not tell you this, but practical experience tells us that you can not fire the timer faster than once about every 50 milliseconds. Windows XP may provide a finer resolution to this limitation, but it is an OS limitation, not a limitation of the language.

We want to use this value to fire our timer events in our animation sequences. We will reuse the code from the earlier program, and place the timer in the "[go]" section. The timer will trigger code in a new labeled section we will call "[move]". Lets take a look at the code:


[go]
    #main.gfx "color white"
    timer 50 , [move]
    wait

Lets simply remove the code fragment that had been associated with the "[go]" label and start our animation sequence over from scratch, since we are going to use a slightly different approach. The key to the new approach, as you will remember is the xchg and ychg values which control how much a movement is made by the ball in the x and y directions during each drawing cycle. In order for this to work correctly we must actually keep track of where the ball is in the x and y directions. These variables will be called xloc and yloc. Each drawing cycle we will calculate a new value for each of these. We will need to track one more pair of coordinates to make the animation work. That is where the red ball is at the beginning of the drawing cycle. Remember that each cycle we blot out the red ball at its current location by drawing a white ball over it, then we calculate the new location for the red ball and draw it at that location. We will call the old location oldx and oldy.

In order for the animation to work correctly coming out of the starting gate (when the user clicks the "go!" button basically) we must first initialize these variables at the beginning of our code. Let's look at that first:


   xchg = -10
   ychg = 1.5
   xloc = 440
   yloc = 20

Obviously, these are the variables we just discussed. Place this code at the very start of your program. Now we are ready to write the initial drawing cycle code. It is very simple. The first thing we want to do is save the current location of the ball. Let's put this code immediately following the "[move]" label:


[move]
    oldx = xloc
    oldy = yloc

Remember the current location of the read ball is xloc and yloc. So you can see that we have saved the values of those variables into the oldx and oldy variables. Now we can update the current location of the ball by adding the xchg and ychg variables to the xloc and yloc variables. This will produce the coordinates where the new ball will be drawn. This is what that looks like:


    xloc = xloc + xchg
    yloc = yloc + ychg

Now we can draw the white ball, blotting out the red ball in its current location. We must first set the background color to white:


    #main.gfx "backcolor white"

Now, as we discussed in the code earlier in this article, we will move the pen to the location where the white ball will be draw using the goto command. Don't forget to lift the pen up! Also, remember we are working with variables in our graphic command strings - we must append these to our command string outside of the string litereal using the semicolon as we discussed earlier. The code looks like this:


    #main.gfx "up; goto ";oldx;" ";int(oldy);"; down; circlefilled 10"

We will also do the same thing for the red ball, but at the xloc and yloc coordinates. Here is how that looks:


    #main.gfx "backcolor red"
    #main.gfx "up; goto ";xloc;" ";int(yloc);"; down; circlefilled 8"

Finally, we include a wait. We only need to draw onw update per cycle, and then wait for the next cycle. The timer will take care of firing the next cycle and we will act on it because we are waiting for the event.

    wait    

The whole, revised program looks like this:

    
'BallAnimation - By Brad Moore
'Placed into the public domain Oct 2004

   xchg = -10
   ychg = 1.5
   xloc = 440
   yloc = 20

    NOMAINWIN
    WindowWidth = 515 : WindowHeight = 303
    UpperLeftX = INT((DisplayWidth-WindowWidth)/2)
    UpperLeftY = INT((DisplayHeight-WindowHeight)/2)

graphicbox  #main.gfx, 10, 10, 480, 210
button      #main.go, "Go!",[go],UL, 385, 230, 105, 25

Open "Window Title" for Window as #main
    #main "trapclose [quit]"
    #main.gfx "down; fill White; flush"
    #main "font ms_sans_serif 10"

[loop]
    Wait

[quit]
    close #main : END

[go]
    #main.gfx "color white"
    timer 50 , [move]
    wait
    
    
[move]
    oldx = xloc
    oldy = yloc
    xloc = xloc + xchg
    yloc = yloc + ychg
    #main.gfx "backcolor white"
    #main.gfx "up; goto ";oldx;" ";int(oldy);"; down; circlefilled 10"
    #main.gfx "backcolor red"
    #main.gfx "up; goto ";xloc;" ";int(yloc);"; down; circlefilled 8"
    wait    

You should be able to execute the program and see the red ball make its way from the upper right corner toward the lower left corner. Unfortunately, the ball does not yet bounce, or even stop for that matter - it continues on into negative territory out of sight until you kill the program, or there is an overflow of some kind. What we need now is to detect boundary collisions and take appropriate action. We have two different types of boundary conditions to consider. They occur when the ball hits either end of the graphicbox, or when it hits the upper or lower edges of the graphicbox. The simplest to handle is the later. As we discussed earlier, all we need to do in this case is change the polarity of the ychg value. This will cause the ball to appear to deflect from the edge at the same angle, but moving in the opposite direction. Changing the polarity (whether the value is negative or positive) is really quite easy. All you need to do is multiply the value times a negative one. This will not alter the value, merely change the sign.

The code is very simple:


ychg = ychg * -1

What we must do is detect the condition when this code must be executed. We will do this in an IF-THEN block. Remember that we know where the ball is located all the time, since we are using xloc and yloc to actually plot the animation of the ball. We also know the bounds of the graphicbox from the code that created it. Looking back at that code, we see that the graphicbox is 210 pixels tall. We can then say if yloc (where the ball is in the 'y' direction) is less than zero, or if it is greater than 210, then lets cause the ball to be deflected. Here is the code that you will want to add to the end of your program (be sure to move the WAIT statement from its current location to the end of this new code block).


    if yloc < 2 or yloc > 208 then 
        ychg = ychg * -1
    end if
    
    wait

To see this in action, change the value of ychg in the initial code at the top of the program so it looks like this:

   ychg = 32

That will cause the ball to take a radically steep angle of approach, demonstrating the deflection we have just added to the program. Go ahead and run the program - the complete copy of the program to this point is available in Appendix A. Notice that the ball still continues out past the left side of the visible area, never to return. What we need to do is trap the other boundary condition where the ball hits the left or right side, and then take action.

Remember that the action to take is made a bit more complex by the special things we want to do when we reach one of these boundaries. Lets review the goals, and then build some pseudo code that accomplishes these.

Here is what we said we wanted to do:

  1. Change the x direction
  2. Alter the speed of the ball (that is the value of the x modifier - it will be called xchg) - remember the apparent speed of the ball will be how many pixels it travels per cycle.
  3. Alter the deflection angle of the ball (in the y direction). This will allow us to slightly change the bouncing action of the ball (making it more or less bouncy in a random manner) when it hits the wall. This is done by changing the y modifier, which we said will be called ychg.

Let's do these tasks in a new labeled sub routine. For now we will call it "[changedirection]". We will goto the sub routine when we detect that we have moved the ball to the left or right side boundary. These are easily known much like the upper and lower boundaries were, as the ball moves in the 'x' direction. When xloc becomes greater than the right most pixel size or less than zero, we will know we are at one of these boundaries. We can detect these the same way we did in the 'y' direction with an IF-THEN statement. For now lets just put it this way - we will refine it later:

If xlox > or < x boundaries then goto [changedirection]

In the sub routine "[changedirection]" we will try to do the three things outlined above:

1. Change ball direction:

   xchg = xchg * -1

2. Alter the speed of the ball:

I want the ball to tend to speed up. I have decided to use a random value that falls between -1 and +2 (inclusive), added to the current rate of 'x' direction change to effect the speed change in the ball. Remember that the apparent speed of the ball is a function of the variable xloc.

   ballspeed = int(rnd(0)*4)-1

Which says take a random number (more than zero and less than one), multiply it by four (which makes it somewhere more than zero and less than four) and subtract one (now it is more than negative one and less than three), and finally return only the whole portion of the result. This will be a -1, 0, 1 or 2. The chances of the ball increasing in speed are greater than it decreasing.

Now the tricky part is we don't know whether to add the ballspeed value to xchg or subtract it, since we have not yet examined whether the ball is moving toward zero or away form it. What I decided to do when considering this was to break the problem into two parts. First I would determine the current direction and then store it. Remember when the ball hits the wall, we want it to rebound, so I need to change the direction also - I can do this in this step easily by storing the desired future direction. The next part was adding the ballspeed change to the xchg value. To do this I will make xchg a positive value using the Absolute Value function called ABS. ABS will make a number positive (its absolute value) regardless of whether it is positive or negative. Once positive I can simply add the ballspeed to the xchg value. The last step (the third step of my two) is changing the polarity of xchg to cause the ball to rebound and continue bouncing.

So, saving the balls direction… We will be reapplying this in the third step, so we will use either a one or a negative one. If the ball is moving toward the zero point in the "x" direction, we know that xchg is negative. Let use that as out test. If xchg is negative, we will want it to be positive later. Save the new value in the variable "direction".

    if xchg < 0 then direction = 1

If I know there will only be two states to a variable I will often take a shortcut and assign one of the states initially as a default, test for the factor that will cause the other state and only change it if that factor is met. I can use that approach here. In the line of code above the one we just created write:

    direction = -1

So that the block now looks like this:


[changedirection]
    ballspeed = int(rnd(0)*4)-1
    direction = -1
    if xchg < 0 then direction = 1

Now we can do the second step - find the absolute value of the xchg variable. If you want, now would be a great time to lookup the ABS function in the Liberty Basic helpfile!

    xchg = abs(xchg)    

Now we can add the value we found for ballspeed to xchg.

    xchg = xchg + ballspeed

And finally we can do the third step, apply the correct polarity we decided on in step one. It is stored in the variable "direction". We can simply multiply xchg by that to accomplish this task.

    xchg = xchg * direction

End this bit of code with a wait statement so we are ready for the next time the timer fires.

   wait

The last thing we need to do before we can run our new program is put in the event handler for when the ball reaches the left or right wall. We know the label of the event handler, now we just need the code to call the handler. Remember that the end of the "[move]" code block looks like this:


    if yloc < 2 or yloc > 208 then 
        ychg = ychg * -1
    end if
    
    wait

We need to stick our event handler right in there just before the wait statement and just after the end if statement. We have already defined the condition earlier in the article. It is when the xloc value is less than the graphicbox low end value (0) or when it is greater than its highend value (480). That sounds like an IF-THEN statement to me. I actually have chosen some value close to those I indicated, but these give a better visual appearance.


    if xloc < -2 or xloc > 482 then [changedirection]

So that little bit of code that ends the "[move]" code block now looks like this:


    if yloc < 2 or yloc > 208 then 
        ychg = ychg * -1
    end if
    if xloc < -2 or xloc > 482 then [changedirection]
    wait

Go ahead and run the program and see what happens!


Goto PREVIOUS section | Goto NEXT section